package com.hrbxlh.virtualization.websocket;

import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Objects;
import com.google.common.collect.*;
import com.hrbxlh.virtualization.dao.StatisticMapper;
import com.hrbxlh.virtualization.dao.StatisticMapperExt;
import com.hrbxlh.virtualization.enums.RoleEnum;
import com.hrbxlh.virtualization.enums.ShareTypeEnum;
import com.hrbxlh.virtualization.listener.ContainerStopDelayTask;
import com.hrbxlh.virtualization.listener.ContainerStopListener;
import com.hrbxlh.virtualization.pojo.Container;
import com.hrbxlh.virtualization.pojo.Statistic;
import com.hrbxlh.virtualization.pojo.StatisticExample;
import com.hrbxlh.virtualization.service.DockerService;
import com.hrbxlh.virtualization.support.SpringContextHolder;
import com.hrbxlh.virtualization.util.SessionUtil;
import com.hrbxlh.virtualization.vo.ShareMessageVO;
import com.hrbxlh.virtualization.vo.UserVO;
import org.apache.commons.collections.CollectionUtils;
import org.joda.time.DateTime;
import org.joda.time.Period;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Set;

/**
 * 实验环境WebSocket
 *
 * @author cheer
 */
@ServerEndpoint(value = "/webSocket/desktop/{courseId}/{chapterId}", configurator = GetHttpSessionConfigurator.class)
public class DesktopWebSocket {

    private static Logger logger = LoggerFactory.getLogger(DesktopWebSocket.class);

    private static SetMultimap<UniqueCourse, UserVO> userMap =
            Multimaps.synchronizedSetMultimap(HashMultimap.create());

    private static ListMultimap<UserInfo, DesktopWebSocket> connectionMap =
            Multimaps.synchronizedListMultimap(ArrayListMultimap.create());

    private static StatisticMapper experimentStatisticMapper = SpringContextHolder.getBean(
            StatisticMapper.class);

    private static StatisticMapperExt statisticMapperExt = SpringContextHolder.getBean(
            StatisticMapperExt.class);

    private static DockerService dockerService = SpringContextHolder.getBean(DockerService.class);

    private Session session;

    private String stuorteaId;

    private String username;

    private Integer roleId;

    private String teacherId;

    private Integer courseId;

    private Integer chapterId;

    private DateTime startTime;

    @OnOpen
    public void onOpen(@PathParam("courseId") Integer courseId, @PathParam("chapterId") Integer chapterId,
                       Session session, EndpointConfig config) {

        this.session = session;
        this.stuorteaId = (String) config.getUserProperties().get("stuorteaId");
        this.username = (String) config.getUserProperties().get("username");
        this.roleId = (Integer) config.getUserProperties().get("roleId");
        this.courseId = courseId;
        this.chapterId = chapterId;

        if(RoleEnum.TEACHER.getCode().equals(roleId)){
            this.teacherId = stuorteaId;
        } else {
            this.teacherId = statisticMapperExt.getTeacherIdByStuorteaIdAndCourseId(stuorteaId, courseId);
        }
        userMap.put(new UniqueCourse(teacherId, courseId, chapterId), new UserVO(stuorteaId, username, roleId));

        UserInfo userInfo = new UserInfo(stuorteaId, courseId, chapterId);
        // 判断当前登入是否为重复登入，如果是重复登入，实验起始时间调整为最初登入的时间
        if (connectionMap.containsKey(userInfo)) {
            DesktopWebSocket desktopWebSocket = connectionMap.get(userInfo).get(0);
            startTime = desktopWebSocket.getStartTime();
        } else {
            startTime = new DateTime();
        }
        connectionMap.put(userInfo, this);
        logger.info("有新连接进入，当前实验的在线人数 - {}", connectionMap.size());
    }

    @OnClose
    public void onClose() {
        UserInfo userInfo = new UserInfo(stuorteaId, courseId, chapterId);
        connectionMap.remove(userInfo, this);
        logger.info("有连接退出，当前实验的在线人数 - {}", connectionMap.size());
        // 如果该用户退出实验环境，则停止容器
        if (!connectionMap.containsKey(userInfo)) {
            userMap.remove(new UniqueCourse(teacherId, courseId, chapterId), new UserVO(stuorteaId, username, roleId));

            ContainerStopListener.getDelayQueue().put(new ContainerStopDelayTask(stuorteaId, courseId, chapterId));

            // 计时统计
            DateTime endTime = new DateTime();
            Period period = new Period(startTime, endTime);
            long duration = (long) period.toStandardSeconds().getSeconds();
            saveExperimentStatistic(stuorteaId, courseId, chapterId, duration);
            logger.info("用户id为{}的同学本次在章节id为{}的实验环境中的实验时长为{}秒", stuorteaId, chapterId,
                    period.toStandardSeconds().getSeconds());

            // 发送关闭分享消息
            ShareMessageVO shareMessageVO = new ShareMessageVO();
            shareMessageVO.setType(ShareTypeEnum.STOP_ALL_SHARE.getCode());
            shareMessageVO.setStuorteaId(stuorteaId);
            shareMessageVO.setUsername(username);
            String message = JSONObject.toJSONString(shareMessageVO);
            Set<UserVO> onlineUserSet = userMap.get(new UniqueCourse(teacherId, courseId, chapterId));
            if (CollectionUtils.isNotEmpty(onlineUserSet)) {
                for (UserVO userVO : onlineUserSet) {
                    sendMessage(userVO.getStuorteaId(), courseId, chapterId, message);
                }
            }
        }
    }

    @OnMessage
    public void onMessage(String jsonStr, Session session) {
        // 暂时不需要实现该方法
    }

    @OnError
    public void onError(Session session, Throwable error) {
        // 暂时不需要实现该方法
    }

    /**
     * 查询在线用户信息
     *
     * @param chapterId 章节Id
     */
    public static Set<UserVO> getUserVOByChapterId(String teacherId, Integer courseId, Integer chapterId) {
        return userMap.get(new UniqueCourse(teacherId, courseId, chapterId));
    }

    /**
     * 查询用户是否在线
     *
     * @param stuorteaId 用户Id
     * @param courseId   课程Id
     * @param chapterId  章节Id
     */
    public static Boolean isOnline(String stuorteaId, Integer courseId, Integer chapterId) {
        return connectionMap.containsKey(new UserInfo(stuorteaId, courseId, chapterId));
    }

    /**
     * 分享实验环境
     *
     * @param container      容器
     * @param stuorteaIdList 收到分享的用户Id集合
     * @param controllable   收到分享的用户是否可以操作实验环境
     */
    public static void shareContainer(Container container, List<String> stuorteaIdList, Boolean controllable) {
        String url = dockerService.queryDesktopUrl(container.getRancherId());
        ShareMessageVO shareMessageVO = new ShareMessageVO();
        shareMessageVO.setType(ShareTypeEnum.SHARE.getCode());
        shareMessageVO.setStuorteaId(SessionUtil.getStuorteaId());
        shareMessageVO.setUsername(SessionUtil.getUsername());
        shareMessageVO.setContainerId(container.getId());
        shareMessageVO.setDesktopUrl(url);
        shareMessageVO.setControllable(controllable);
        String message = JSONObject.toJSONString(shareMessageVO);

        for (String stuorteaId : stuorteaIdList) {
            sendMessage(stuorteaId, container.getCourseId(), container.getChapterId(), message);
        }
    }

    /**
     * 关闭分享实验环境
     *
     * @param container 容器
     */
    public static void stopShareContainer(Container container) {
        ShareMessageVO shareMessageVO = new ShareMessageVO();
        shareMessageVO.setType(ShareTypeEnum.STOP_SHARE.getCode());
        shareMessageVO.setStuorteaId(SessionUtil.getStuorteaId());
        shareMessageVO.setUsername(SessionUtil.getUsername());
        shareMessageVO.setContainerId(container.getId());
        String message = JSONObject.toJSONString(shareMessageVO);

        String teacherId;
        if (SessionUtil.getRoleId().equals(RoleEnum.TEACHER.getCode())) {
            teacherId = SessionUtil.getStuorteaId();
        } else {
            teacherId = statisticMapperExt.getTeacherIdByStuorteaIdAndCourseId(SessionUtil.getStuorteaId(),
                    container.getCourseId());
        }

        Set<UserVO> onlineUserSet = userMap.get(new UniqueCourse(teacherId, container.getCourseId(),
                container.getChapterId()));
        if (CollectionUtils.isNotEmpty(onlineUserSet)) {
            for (UserVO userVO : onlineUserSet) {
                sendMessage(userVO.getStuorteaId(), container.getCourseId(), container.getChapterId(), message);
            }
        }
    }

    /**
     * 保存实验时间
     *
     * @param stuorteaId 学生Id
     * @param chapterId  章节Id
     * @param duration   时长
     */
    private void saveExperimentStatistic(String stuorteaId, Integer courseId, Integer chapterId, long duration) {
        StatisticExample experimentStatisticExample = new StatisticExample();
        experimentStatisticExample.or().andStuorteaIdEqualTo(stuorteaId)
                .andCourseIdEqualTo(courseId)
                .andChapterIdEqualTo(chapterId);
        List<Statistic> experimentStatisticList = experimentStatisticMapper.selectByExample(
                experimentStatisticExample);
        if (CollectionUtils.isEmpty(experimentStatisticList)) {
            Statistic experimentStatistic = new Statistic();
            experimentStatistic.setStuorteaId(stuorteaId);
            experimentStatistic.setCourseId(courseId);
            experimentStatistic.setChapterId(chapterId);
            experimentStatistic.setExperimentTime(duration);
            experimentStatisticMapper.insertSelective(experimentStatistic);
        } else {
            Statistic experimentStatistic = experimentStatisticList.get(0);
            experimentStatistic.setExperimentTime(duration + experimentStatistic.getExperimentTime());
            experimentStatisticMapper.updateByPrimaryKeySelective(experimentStatistic);
        }
    }

    /**
     * 发送消息
     *
     * @param stuorteaId 接受消息的用户Id
     * @param chapterId  章节Id
     * @param message    消息内容
     */
    private static void sendMessage(String stuorteaId, Integer courseId, Integer chapterId, String message) {
        UserInfo userInfo = new UserInfo(stuorteaId, courseId, chapterId);
        List<DesktopWebSocket> desktopWebSocketList = connectionMap.get(userInfo);
        if (CollectionUtils.isNotEmpty(desktopWebSocketList)) {
            for (DesktopWebSocket desktopWebSocket : desktopWebSocketList) {
                try {
                    desktopWebSocket.getSession().getBasicRemote().sendText(message);
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    private static class UserInfo {

        private String userId;

        private Integer courseId;

        private Integer chapterId;

        public UserInfo() {
        }

        public UserInfo(String userId, Integer courseId, Integer chapterId) {
            this.userId = userId;
            this.courseId = courseId;
            this.chapterId = chapterId;
        }

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId;
        }

        public Integer getCourseId() {
            return courseId;
        }

        public void setCourseId(Integer courseId) {
            this.courseId = courseId;
        }

        public Integer getChapterId() {
            return chapterId;
        }

        public void setChapterId(Integer chapterId) {
            this.chapterId = chapterId;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            UserInfo userInfo = (UserInfo) o;
            return com.google.common.base.Objects.equal(userId, userInfo.userId) &&
                    com.google.common.base.Objects.equal(courseId, userInfo.courseId) &&
                    com.google.common.base.Objects.equal(chapterId, userInfo.chapterId);
        }

        @Override
        public int hashCode() {
            return com.google.common.base.Objects.hashCode(userId, courseId, chapterId);
        }
    }

    private static class UniqueCourse {

        private String stuorteaId;

        private Integer courseId;

        private Integer chapterId;

        public UniqueCourse() {
        }

        public UniqueCourse(String stuorteaId, Integer courseId, Integer chapterId) {
            this.stuorteaId = stuorteaId;
            this.courseId = courseId;
            this.chapterId = chapterId;
        }

        public String getStuorteaId() {
            return stuorteaId;
        }

        public void setStuorteaId(String stuorteaId) {
            this.stuorteaId = stuorteaId;
        }

        public Integer getCourseId() {
            return courseId;
        }

        public void setCourseId(Integer courseId) {
            this.courseId = courseId;
        }

        public Integer getChapterId() {
            return chapterId;
        }

        public void setChapterId(Integer chapterId) {
            this.chapterId = chapterId;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            UniqueCourse that = (UniqueCourse) o;
            return Objects.equal(stuorteaId, that.stuorteaId) &&
                    Objects.equal(courseId, that.courseId) &&
                    Objects.equal(chapterId, that.chapterId);
        }

        @Override
        public int hashCode() {
            return Objects.hashCode(stuorteaId, courseId, chapterId);
        }
    }

    public String getStuorteaId() {
        return stuorteaId;
    }

    public Integer getCourseId() {
        return courseId;
    }

    public Integer getChapterId() {
        return chapterId;
    }

    public String getUsername() {
        return username;
    }

    public Integer getRoleId() {
        return roleId;
    }

    public String getTeacherId() {
        return teacherId;
    }

    public DateTime getStartTime() {
        return startTime;
    }

    public Session getSession() {
        return session;
    }
}
